package mh.map.objectset;

import java.awt.Color;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.TreeMap;
import java.util.Vector;

import mh.entity.object.EntityObject;
import mh.map.CachedResources;
import mh.map.Texture;

/**
 * <p>
 * ObjectSet handles operations on tiles as a set, or group. It has several advanced internal functions aimed at
 * reducing unnecessary data replication.
 * </p>
 * 
 */
public class ObjectSet implements Iterable<EntityObject> {

	private String									source;
	private String									name;
	private String									imgSource;

	private String									base;

	/** This is the map of the object inside this set */
	final private TreeMap<Integer, EntityObject>	objects	= new TreeMap<Integer, EntityObject>();

	/** The color that has to be used as transparent color */
	private Color									transparentColor;

	/** The texture used by all the objects. */
	protected Texture								texture;

	/**
	 * Default constructor
	 */
	public ObjectSet() {
	}

	/**
	 * Create a tileset from the image file. The texture is stored in the <code>TileSet</code> and a reference of it is
	 * stored in each <code>Tile</code>. The texture stay whole in all the process, no cuts are made.
	 * 
	 * @param ref
	 */
	public void importObjectTexture(String ref) {

		OSXImporter imp = new OSXImporter(ref);
		Vector<EntityObject> obj = imp.getObjects();
		if (obj != null) {
			for (EntityObject o : obj) {
				this.addObject(o);
			}
		}
		CachedResources.singleton().loaded(this, this.objects.keySet());
	}

	/**
	 * Sets the URI path of the external source of this object set. By setting this, the set is implied to be external
	 * in all other operations.
	 * 
	 * @param s
	 *            a URI of the objectset image file
	 */
	public void setSource(String s) {
		this.source = s;
	}

	/**
	 * Sets the name of this tileset.
	 * 
	 * @param name
	 *            the new name for this tileset
	 */
	public void setName(String name) {
		this.name = name;
	}

	/**
	 * @return the name of this tileset.
	 */
	public String getName() {
		return this.name;
	}

	/**
	 * Returns the image that will be cutted once created a "tile"
	 * 
	 * @return the whole image
	 */
	public Texture getTexture() {
		return this.texture;
	}

	/**
	 * @return the imgSource
	 */
	public String getImgSource() {
		return this.imgSource;
	}

	/**
	 * @param imgSource
	 *            the imgSource to set
	 */
	public void setImgSource(String imgSource) {
		this.imgSource = imgSource;
	}

	/**
	 * Sets the transparent color in the tileset image.
	 * 
	 * @param color
	 */
	public void setTransparentColor(Color color) {
		this.transparentColor = color;
	}

	/**
	 * Returns the transparent color of the tileset image, or <code>null</code> if none is set.
	 * 
	 * @return Color - The transparent color of the set
	 */
	public Color getTransparentColor() {
		return this.transparentColor;
	}

	/**
	 * Adds the tile to the set, setting the id of the tile only if the current value of id is -1.
	 * 
	 * @param o
	 *            the tile to add
	 */
	private void addObject(EntityObject o) {
		this.objects.put(o.getRender().getEntityDatabaseID(), o);
		o.setObjectSet(this);
	}

	/**
	 * Removes a tile from this tileset. Does not invalidate other tile indices. Removal is simply setting the reference
	 * at the specified index to <b>null</b>.
	 * 
	 * @param i
	 *            the index to remove
	 */
	public void removeObject(int i) {
		this.objects.remove(i);
	}

	/**
	 * Returns the amount of tiles in this tileset.
	 * 
	 * @return the amount of tiles in this tileset
	 */
	public int size() {
		return this.objects.size();
	}

	/**
	 * @param id
	 *            The ID to search
	 * @return True if the ID is present in this objectset.
	 */
	public boolean contains(int id) {
		return this.objects.containsKey(id);
	}

	/**
	 * Returns an iterator over the tiles in this tileset.
	 * 
	 * @return an iterator over the tiles in this tileset.
	 */
	@Override
	public Iterator<EntityObject> iterator() {
		return this.objects.values().iterator();
	}

	/**
	 * Gets the object with <b>global</b> id form database <code>i</code>.
	 * 
	 * @param i
	 *            id from database of the Object
	 * @return The EntityObject with that specific id from database or null otherwise.
	 */
	public EntityObject getObject(int i) {
		CachedResources.singleton().hearthBeat(this);
		//		return this.objects.get(i);
		try {
			return (EntityObject) this.unmarshalClass(this.objects.get(i));
		} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
			e.printStackTrace();
			return null;
		}
	}

	private Object unmarshalClass(Object e) throws InstantiationException, IllegalAccessException, InvocationTargetException {
		Class<?> reflector = e.getClass();
		Constructor<?> cons = null;
		try {
			cons = reflector.getConstructor(reflector);
		} catch (SecurityException e1) {
			e1.printStackTrace();
		} catch (NoSuchMethodException e1) {
			e1.printStackTrace();
			return null;
		}
		Object o = cons.newInstance(e);

		return o;
	}

	/**
	 * Returns the source of this tileset.
	 * 
	 * @return a filename if tileset is external or <code>null</code> if tileset is internal.
	 */
	public String getSource() {
		return this.source;
	}

	/**
	 * Returns the base directory for the tileset
	 * 
	 * @return a directory in native format as given in the tileset file or tag
	 */
	public String getBaseDir() {
		return this.base;
	}

	/**
	 * @return the name of the tileset, and the total tiles
	 */
	@Override
	public String toString() {
		return this.getName() + " [" + this.size() + "]";
	}

}
